// XHeaderCtrl.cpp  Version 1.4
//
// Author:  Hans Dietrich
//          hdietrich@gmail.com
//
// This code is based on "Outlook 98-Style FlatHeader Control" 
// by Maarten Hoeben.
//
// See http://www.codeguru.com/listview/FlatHeader.shtml
//
// License:
//     This software is released into the public domain.  You are free to use
//     it in any way you like, except that you may not sell this source code.
//
//     This software is provided "as is" with no expressed or implied warranty.
//     I accept no liability for any damage or loss of business that this 
//     software may cause.
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "XHeaderCtrl.h"
//08-01-03, andy, use XGraph's CMemDC
#include "memdc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CXHeaderCtrl

IMPLEMENT_DYNCREATE(CXHeaderCtrl, CHeaderCtrl)

BEGIN_MESSAGE_MAP(CXHeaderCtrl, CHeaderCtrl)
	//{{AFX_MSG_MAP(CXHeaderCtrl)
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
	ON_WM_SYSCOLORCHANGE()
	ON_WM_LBUTTONDBLCLK()
	//}}AFX_MSG_MAP
	ON_MESSAGE(HDM_INSERTITEMA, OnInsertItem)
	ON_MESSAGE(HDM_INSERTITEMW, OnInsertItem)
	ON_MESSAGE(HDM_DELETEITEM, OnDeleteItem)
	ON_MESSAGE(HDM_SETIMAGELIST, OnSetImageList)
	ON_MESSAGE(HDM_LAYOUT, OnLayout)
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////////
// ctor
CXHeaderCtrl::CXHeaderCtrl()
{
	m_cr3DHighLight   = ::GetSysColor(COLOR_3DHIGHLIGHT);
	m_cr3DShadow      = ::GetSysColor(COLOR_3DSHADOW);
	m_cr3DFace        = ::GetSysColor(COLOR_3DFACE);
	m_crBtnText       = ::GetSysColor(COLOR_BTNTEXT);

	m_pListCtrl       = NULL;			//+++
	m_nFormat         = DT_DEFAULT;		//+++
	m_rgbText         = m_crBtnText;	//+++
	m_bDividerLines   = TRUE;			//+++
	m_bDoubleBuffer   = TRUE;
	m_iSpacing        = 6;
	m_sizeArrow.cx    = 8;
	m_sizeArrow.cy    = 8;
	m_sizeImage.cx    = 0;
	m_sizeImage.cy    = 0;
	m_bStaticBorder   = FALSE;
	m_nDontDropCursor = 0;
	m_bResizing       = FALSE;
	m_nClickFlags     = 0;
}

///////////////////////////////////////////////////////////////////////////////
// dtor
CXHeaderCtrl::~CXHeaderCtrl()
{
}

///////////////////////////////////////////////////////////////////////////////
// ModifyProperty
BOOL CXHeaderCtrl::ModifyProperty(WPARAM wParam, LPARAM lParam)
{
	switch(wParam)
	{
		case FH_PROPERTY_SPACING:
			m_iSpacing = (int)lParam;
			break;

		case FH_PROPERTY_ARROW:
			m_sizeArrow.cx = LOWORD(lParam);
			m_sizeArrow.cy = HIWORD(lParam);
			break;

		case FH_PROPERTY_STATICBORDER:
			m_bStaticBorder = (BOOL)lParam;
			break;

		case FH_PROPERTY_DONTDROPCURSOR:
			m_nDontDropCursor = (UINT)lParam;
			break;

		default:
			return FALSE;
	}

	Invalidate();
	return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// DrawCtrl
void CXHeaderCtrl::DrawCtrl(CDC* pDC)
{
	CRect rectClip;
	if (pDC->GetClipBox(&rectClip) == ERROR)
		return;

	//CString t("");
	//t.Format(_T("rectClip(%d,%d,%d,%d)\n"), rectClip.left, rectClip.top, rectClip.Width(), rectClip.Height());
	//OutputDebugString(t);

	CRect rectClient, rectItem;
	GetClientRect(&rectClient);
	//t.Format(_T("rectClient(%d,%d,%d,%d)\n"), rectClient.left, rectClient.top, rectClient.Width(), rectClient.Height());
	//OutputDebugString(t);

    pDC->FillSolidRect(rectClip, m_cr3DFace);

	int iItems = GetItemCount();
	ASSERT(iItems >= 0);
	
	//OutputDebugString(_T("iItem\n"));
	CPen penHighLight(PS_SOLID, 1, m_cr3DHighLight);
	CPen penShadow(PS_SOLID, 1, m_cr3DShadow);
	CPen* pPen = pDC->GetCurrentPen();

	CFont* pFont = pDC->SelectObject(GetFont());

	pDC->SetBkColor(m_cr3DFace);
	pDC->SetTextColor(m_crBtnText);

	int iWidth = 0;

	for (int i = 0; i < iItems; i++)
	{
		int iItem = OrderToIndex(i);

		TCHAR szText[FLATHEADER_TEXT_MAX];

		HDITEM hditem;
		hditem.mask = HDI_WIDTH|HDI_FORMAT|HDI_TEXT|HDI_IMAGE|HDI_BITMAP;
		hditem.pszText = szText;
		hditem.cchTextMax = sizeof(szText);
		GetItem(iItem, &hditem);

		GetItemRect(iItem, rectItem);
		//t.Format(_T("rectItem(%d,%d,%d,%d)\n"), rectItem.left, rectItem.top, rectItem.Width(), rectItem.Height());
		//OutputDebugString(t);


		if (rectItem.right >= rectClip.left || rectItem.left <= rectClip.right)
		{
			if (hditem.fmt & HDF_OWNERDRAW)
			{
				DRAWITEMSTRUCT disItem;
				disItem.CtlType = ODT_BUTTON;
				disItem.CtlID = GetDlgCtrlID();
				disItem.itemID = iItem;
				disItem.itemAction = ODA_DRAWENTIRE;
				disItem.itemState = 0;
				disItem.hwndItem = m_hWnd;
				disItem.hDC = pDC->m_hDC;
				disItem.rcItem = rectItem;
				disItem.itemData = 0;

				DrawItem(&disItem);
			}
			else
			{
				rectItem.DeflateRect(m_iSpacing, 0);
				DrawItem(pDC, rectItem, &hditem);
				rectItem.InflateRect(m_iSpacing, 0);

				//if (m_nClickFlags & MK_LBUTTON && m_iHotIndex == iItem && m_hdhtiHotItem.flags & HHT_ONHEADER)
				//	pDC->InvertRect(rectItem);
			}

			if (i < iItems-1)
			{
				// draw divider lines
				if (m_bDividerLines)		//+++
				{
					pDC->SelectObject(&penShadow);
					pDC->MoveTo(rectItem.right-1, rectItem.top+2);
					pDC->LineTo(rectItem.right-1, rectItem.bottom-2);

					pDC->SelectObject(&penHighLight);
					pDC->MoveTo(rectItem.right, rectItem.top+2);
					pDC->LineTo(rectItem.right, rectItem.bottom-2);
				}
			}
		}

		iWidth += hditem.cxy;
	}

	if (iWidth > 0)
	{
		rectClient.right = rectClient.left + iWidth;
		pDC->Draw3dRect(rectClient, m_cr3DHighLight, m_cr3DShadow);
	}

	pDC->SelectObject(pFont);
	pDC->SelectObject(pPen);

	penHighLight.DeleteObject();
	penShadow.DeleteObject();
}

///////////////////////////////////////////////////////////////////////////////
// DrawItem
void CXHeaderCtrl::DrawItem(LPDRAWITEMSTRUCT)
{
	ASSERT(FALSE);  // must override for self draw header controls
	return;
}

///////////////////////////////////////////////////////////////////////////////
// DrawItem
void CXHeaderCtrl::DrawItem(CDC* pDC, CRect rect, LPHDITEM lphdi)
{
	ASSERT(lphdi->mask & HDI_FORMAT);
	//OutputDebugString(_T("DrawItem\n"));
	int iWidth = 0;

	CBitmap* pBitmap = NULL;
	BITMAP BitmapInfo;

	if (lphdi->fmt & HDF_BITMAP)
	{
		ASSERT(lphdi->mask & HDI_BITMAP);
		ASSERT(lphdi->hbm);

		pBitmap = CBitmap::FromHandle(lphdi->hbm);
		if (pBitmap)
		{
			pBitmap->GetObject(sizeof(BITMAP), &BitmapInfo);
		}
	}

	rect.left += ((iWidth = DrawImage(pDC, rect, lphdi, FALSE)) != 0) ? 
		iWidth + m_iSpacing : 0;

	rect.right -= ((iWidth = DrawBitmap(pDC, rect, lphdi, pBitmap, &BitmapInfo, TRUE)) != 0) ? 
		iWidth + m_iSpacing : 0;

	//CString t("");
	//t.Format(_T("DrawText rect(%d,%d,%d,%d)\n"), rect.left, rect.top, rect.Width(), rect.Height());
	//OutputDebugString(t);

	DrawText(pDC, rect, lphdi);

}

///////////////////////////////////////////////////////////////////////////////
// DrawImage
int CXHeaderCtrl::DrawImage(CDC* pDC, CRect rect, LPHDITEM lphdi, BOOL bRight)
{
	int iWidth = 0;

	if (lphdi->iImage != XHEADERCTRL_NO_IMAGE)
	{
		CImageList* pImageList = GetImageList();

		if (pImageList && (rect.Width() > 0))
		{
			POINT point;

			point.y = rect.CenterPoint().y - ((m_sizeImage.cy+1) >> 1);

			if (bRight)
				point.x = rect.right - m_sizeImage.cx;
			else
				point.x = rect.left;

			SIZE size;
			size.cx = rect.Width() < m_sizeImage.cx ? rect.Width() : m_sizeImage.cx;
			size.cy = m_sizeImage.cy;

			// save image list background color
			COLORREF rgb = pImageList->GetBkColor();

			// set image list background color to same as header control
			pImageList->SetBkColor(pDC->GetBkColor());
			pImageList->DrawIndirect(pDC, lphdi->iImage, point, size, CPoint(0, 0));
			pImageList->SetBkColor(rgb);

			iWidth = m_sizeImage.cx;
		}
		else if (rect.Width() > 0)
		{
			// no image list, just draw checkbox

			CRect chkboxrect = rect;

			// center the checkbox

			int h = 13;	// height and width are the same
			chkboxrect.right = chkboxrect.left + h;
			chkboxrect.top = rect.top + (rect.Height() - h) / 2;
			chkboxrect.bottom = chkboxrect.top + h;

			// fill rect inside checkbox with white
			COLORREF rgbBackground = pDC->SetBkColor(::GetSysColor(COLOR_WINDOW));
			pDC->FillSolidRect(&chkboxrect, ::GetSysColor(COLOR_WINDOW));

			// draw border
			CBrush brush(RGB(51,102,153));
			pDC->FrameRect(&chkboxrect, &brush);

			if (lphdi->iImage == XHEADERCTRL_CHECKED_IMAGE)
			{
				CPen blackpen(PS_SOLID, 1, RGB(51,153,51));
				CPen * pOldPen = pDC->SelectObject(&blackpen);

				// draw the checkmark
				int x = chkboxrect.left + 9;
				ASSERT(x < chkboxrect.right);
				int y = chkboxrect.top + 3;
				int i = 0;
				for (i = 0; i < 4; i++)
				{
					pDC->MoveTo(x, y);
					pDC->LineTo(x, y+3);
					x--;
					y++;
				}
				for (i = 0; i < 3; i++)
				{
					pDC->MoveTo(x, y);
					pDC->LineTo(x, y+3);
					x--;
					y--;
				}

				if (pOldPen)
					pDC->SelectObject(pOldPen);
			}

			pDC->SetBkColor(rgbBackground);

			iWidth = chkboxrect.Width();
		}
		else
		{
			// width = 0, do nothing
		}
	}

	return iWidth;
}

///////////////////////////////////////////////////////////////////////////////
// DrawBitmap
int CXHeaderCtrl::DrawBitmap(CDC* pDC, 
								CRect rect, 
								LPHDITEM lphdi, 
								CBitmap* pBitmap, 
								BITMAP* pBitmapInfo, 
								BOOL bRight)
{
	UNUSED_ALWAYS(lphdi);

	int iWidth = 0;

	if (pBitmap)
	{
		iWidth = pBitmapInfo->bmWidth;
		if (iWidth <= rect.Width() && rect.Width() > 0)
		{
			POINT point;

			point.y = rect.CenterPoint().y - (pBitmapInfo->bmHeight >> 1);

			if (bRight)
				point.x = rect.right - iWidth;
			else
				point.x = rect.left;

			CDC dc;
			if (dc.CreateCompatibleDC(pDC) == TRUE) 
			{
				CBitmap * pOldBitmap = dc.SelectObject(pBitmap);
				iWidth = pDC->BitBlt(
					point.x, point.y, 
					pBitmapInfo->bmWidth, pBitmapInfo->bmHeight, 
					&dc, 
					0, 0, 
					SRCCOPY
				) ? iWidth:0;
				dc.SelectObject(pOldBitmap);
			}
			else 
				iWidth = 0;
		}
		else
			iWidth = 0;
	}

	return iWidth;
}

///////////////////////////////////////////////////////////////////////////////
// DrawText
int CXHeaderCtrl::DrawText(CDC* pDC, CRect rect, LPHDITEM lphdi)
{
	CSize size = pDC->GetTextExtent(lphdi->pszText);

#if 0  // -----------------------------------------------------------
	pDC->SetTextColor(RGB(0,0,255));

	if (rect.Width() > 0 && lphdi->mask & HDI_TEXT && lphdi->fmt & HDF_STRING)
	{
		size = pDC->GetTextExtent(lphdi->pszText);

		// always center column headers
		pDC->DrawText(lphdi->pszText, -1, rect, 
			DT_CENTER|DT_END_ELLIPSIS|DT_SINGLELINE|DT_VCENTER);
	}
#endif // -----------------------------------------------------------

	//+++

	if (rect.Width() > 0 && lphdi->mask & HDI_TEXT && lphdi->fmt & HDF_STRING)
	{
		pDC->SetTextColor(m_rgbText);

		UINT nFormat = m_nFormat;

		if (nFormat == DT_DEFAULT)
		{
			// default to whatever alignment the column is set to

			if (lphdi->fmt & LVCFMT_CENTER)
				nFormat = DT_CENTER;
			else if (lphdi->fmt & LVCFMT_RIGHT)
				nFormat = DT_RIGHT;
			else
				nFormat = DT_LEFT;
		}

		pDC->DrawText(lphdi->pszText, -1, rect, 
			nFormat | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER);
	}

	size.cx = rect.Width() > size.cx ? size.cx : rect.Width();

	return size.cx > 0 ? size.cx : 0;
}

///////////////////////////////////////////////////////////////////////////////
// OnInsertItem
LRESULT CXHeaderCtrl::OnInsertItem(WPARAM, LPARAM)
{
	return Default();
}

///////////////////////////////////////////////////////////////////////////////
// OnDeleteItem
LRESULT CXHeaderCtrl::OnDeleteItem(WPARAM, LPARAM)
{
	return Default();
}

///////////////////////////////////////////////////////////////////////////////
// OnSetImageList
LRESULT CXHeaderCtrl::OnSetImageList(WPARAM, LPARAM lParam)
{
	CImageList* pImageList;
	pImageList = CImageList::FromHandle((HIMAGELIST)lParam);

	IMAGEINFO info;
	if (pImageList->GetImageInfo(0, &info))
	{
		m_sizeImage.cx = info.rcImage.right - info.rcImage.left;
		m_sizeImage.cy = info.rcImage.bottom - info.rcImage.top;
	}

	return Default();
}

///////////////////////////////////////////////////////////////////////////////
// OnLayout
LRESULT CXHeaderCtrl::OnLayout(WPARAM, LPARAM lParam)
{
	LPHDLAYOUT lphdlayout = (LPHDLAYOUT)lParam;

	if (m_bStaticBorder)
		lphdlayout->prc->right += GetSystemMetrics(SM_CXBORDER)*2;

	return CHeaderCtrl::DefWindowProc(HDM_LAYOUT, 0, lParam);
}

///////////////////////////////////////////////////////////////////////////////
// OnSysColorChange
void CXHeaderCtrl::OnSysColorChange() 
{
	XLISTCTRL_TRACE(_T("in CXHeaderCtrl::OnSysColorChange\n"));

	CHeaderCtrl::OnSysColorChange();
	
	m_cr3DHighLight = ::GetSysColor(COLOR_3DHIGHLIGHT);
	m_cr3DShadow    = ::GetSysColor(COLOR_3DSHADOW);
	m_cr3DFace      = ::GetSysColor(COLOR_3DFACE);
	m_crBtnText     = ::GetSysColor(COLOR_BTNTEXT);
}

///////////////////////////////////////////////////////////////////////////////
// OnEraseBkgnd
BOOL CXHeaderCtrl::OnEraseBkgnd(CDC* pDC) 
{
	UNUSED_ALWAYS(pDC);
	return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// OnPaint
void CXHeaderCtrl::OnPaint() 
{
    CPaintDC dc(this);

    if (m_bDoubleBuffer)
    {
		//08-01-03, andy, use XGraph's new CMemDC class
        //CMemDC MemDC(&dc);
		CRect rect;   
		GetClientRect(&rect);   
		CMemDC MemDC(&dc,rect);   
        DrawCtrl(&MemDC);

		//CString t("");
		//t.Format(_T("DrawCtrl(&MemDC)\n"));
		//OutputDebugString(t);

    }
    else
	{
//		CString t("");
//		t.Format(_T("DrawCtrl(&dc)\n"));
//		OutputDebugString(t);
		DrawCtrl(&dc);
	}
}

void CXHeaderCtrl::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	XLISTCTRL_TRACE(_T("in CXHeaderCtrl::OnLButtonDblClk\n"));

	SendMessage(WM_LBUTTONDOWN, nFlags, MAKELPARAM(point.x, point.y));

	//CHeaderCtrl::OnLButtonDown(nFlags, point);
}
